前端切片上传文件(可暂停),以链接形式返回

您所在的位置:网站首页 js 暂停 前端切片上传文件(可暂停),以链接形式返回

前端切片上传文件(可暂停),以链接形式返回

#前端切片上传文件(可暂停),以链接形式返回| 来源: 网络整理| 查看: 265

前言

不管是在使用他人的网页还是自己设计的网页,有一项功能可以说是很常见的,上传文件。常见的有图片(.jpg .png)、音频(.MP3 .MP4),文档(.doc)等。一般都要前后端的配合,但是懂得都懂前端还有NodeJS,所以开始干活。(一定还有更厉害的方案,更厉害的代码,有什么更好的实现,可以在评论中说说,最好直接上代码)

样例展示

动画.gif

image.png

搭环境

我们还是使用我们稍微熟悉点的NodeJS框架-KOA2

安装依赖 npm install koa --save npm install koa-router --save npm i formidable --save npm install koa-static --save npm i @koa/cors --save 测试环境

环境没有问题

var Koa = require('koa'); var app = new Koa(); var Router = require('koa-router')(); const fs = require('fs') const formidable = require('formidable') const path = require('path') const koaStatic = require('koa-static') app.use(koaStatic(path.join(__dirname, 'public'))) //开启跨域 const cors = require("@koa/cors") app.use(cors()) app.use(async (ctx, next) => { ctx.set('Access-Control-Allow-Origin', '*'); ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'); ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); if (ctx.method == 'OPTIONS') { ctx.body = 200; } else { await next(); } }); Router.get('/', async (ctx) => { ctx.body = "ok" }) app .use(Router.routes()) //启动路由 .use(Router.allowedMethods()); app.listen(3000);

image.png

思路及实现 前端思路

既然是文件的切片上传,那么切片要有吧,我们使用Blob的slice方法,我们就可以对二进制文件进行拆分。一个个文件有了那就上传吧,我们可以使用axios上传文件,最后差一个控制暂停和继续的,可以理解为取消请求和重新发送请求,只不过在重新发送请求的时候要判断哪些切片是上传过的。(代码有点长,尽量打上注释)

前端代码 继续 暂停 import { onMounted, ref } from "vue"; import axios from "axios"; //为下面取消请求做准备 const CancelToken = axios.CancelToken; export default { name: "App", setup() { let img = ref(); //用来计算进度条 (alreadyUpload/uploadTotal)*100 let alreadyUpload = ref(0); let uploadTotal = ref(1); //请求取消的数组 let cancel = ref([]); let isChange = ref(false); async function onChange() { let sum = []; let file = img.value.files[0]; let size = 1024 * 20; //20kB 切片大小,可以调大点 let fileChunks = []; let index = 0; //切片序号 isChange.value = true; // 获取已上传的切片 await axios({ method: "get", url: "http://127.0.0.1:3000/alreadyFile", params: { filename: file.name, }, //用于取消请求 cancelToken: new CancelToken(function executor(c) { cancel.value.push(c); }), }) .then((response) => { //返回值是一个已上传切片数组 sum = response.data; alreadyUpload.value = sum.length; }) // eslint-disable-next-line @typescript-eslint/no-empty-function .catch(() => {}); // 对二进制文件进行切片 for (let cur = 0; cur < file.size; cur += size) { fileChunks.push({ hash: index++, chunk: file.slice(cur, cur + size), }); } uploadTotal.value = fileChunks.length; // 判断切片是否全部上传完毕 if (sum.length != fileChunks.length) { for (let i = 0; i < fileChunks.length; i++) { let item = fileChunks[i]; let formData = new FormData(); formData.append("filename", file.name); formData.append("hash", String(item.hash)); formData.append("chunk", item.chunk); //判断某个切片是否上传完毕 if (sum.indexOf(item.hash) == -1) { // 上传切片 axios({ method: "post", url: "http://127.0.0.1:3000/upload", data: formData, cancelToken: new CancelToken(function executor(c) { cancel.value.push(c); }), }) .then(() => { alreadyUpload.value = alreadyUpload.value + 1; }) // eslint-disable-next-line @typescript-eslint/no-empty-function .catch(() => {}); } else { continue; } } } else { console.log("上传已结束"); } //请求文件链接 await axios({ method: "get", url: "http://127.0.0.1:3000/fileLink", params: { filename: file.name, }, cancelToken: new CancelToken(function executor(c) { cancel.value.push(c); }), }) .then((response) => { //返回文件链接 console.log(response.data); }) // eslint-disable-next-line @typescript-eslint/no-empty-function .catch(() => {}); } //点击暂停按钮 const noSuspend = () => { console.log("请求已取消"); cancel.value.forEach((val) => { val(); }); }; //点击继续按钮 const onContinue = () => { if (isChange.value) { onChange(); } }; return { img, onChange, alreadyUpload, uploadTotal, noSuspend, onContinue, }; }, }; .progress-frame { width: 500px; height: 32px; background-color: aqua; } .progress { width: v-bind("(alreadyUpload/uploadTotal)*100+'%'"); height: 32px; background-color: red; } 后端思路及代码

这个地方合在一起说,后端我们准备两个文件夹一个用来保存同一个文件的切片,一个用来保存切片合并后的文件(也就是链接返回的文件)。

image.png

在全局里配置路径,一个是切片的文件目录,一个是切片合并后的文件目录

const TEMPORARY_FILES = path.join(__dirname, 'temporary') const STATIC_FILES = path.join(__dirname, 'public') 获取已上传切片

添加一个路由,返回已上传切片的名字数组

Router.get("/alreadyFile", async (ctx) => { const { filename } = ctx.query; const already = [] //fs.readdirSync,该方法将返回一个包含“指定目录下所有文件名称”的数组对象 //切片文件目下没有 `${filename}`的文件夹,那就是还没开始上传 if (!fs.existsSync(`${TEMPORARY_FILES}\\${filename}`)) { ctx.body = already; } else { //有文件夹,那就获取该文件夹下面切片的文件名 fs.readdirSync(`${TEMPORARY_FILES}\\${filename}`).forEach((name) => { already.push(Number(name)) }) ctx.body = already } })

返回的就是 0-18的字符串数组['1','2','3',...,'18']

image.png

切片的上传

添加路由

主要功能是创建切片,并放入同一个文件夹,再把文件夹放入切片目录下。也是重点。

Router.post('/upload', async (ctx) => { let form = new formidable.IncomingForm(); form.parse(ctx.req, (err, value, files) => { //切片保存在temporary目录下的那个文件夹下 let dir = `${TEMPORARY_FILES}\\${value.filename}` //第几个切片 let hash = value.hash; let chunk = files.chunk; const buffer = fs.readFileSync(chunk.filepath) try { // 是否存在这个文件夹 if (!fs.existsSync(dir)) { //创建 fs.mkdirSync(dir) } // 创建切片文件 const ws = fs.createWriteStream(`${dir}\\${hash}`) // 切片写入 ws.write(buffer) ws.close() } catch (error) { console.error(error) } }) ctx.body = "ok" }) 返回文件链接

添加路由

主要功能是看是否存已经合并的文件,如果存在返回文件链接,否则先合并切片再返回文件链接。

Router.get('/fileLink', async (ctx) => { const { filename } = ctx.query try { // 在public目录下是否存在这个文件,有就直接返回链接,ctx.origin获取域名 if (fs.existsSync(`${STATIC_FILES}\\${filename}`)) { ctx.body = { "url": `${ctx.origin}/${filename}` } } } catch (error) { console.error(error); } try { let len = 0 //fs.readdirSync,该方法将返回一个包含“指定目录下所有文件名称”的数组对象 const bufferList = fs.readdirSync(`${TEMPORARY_FILES}\\${filename}`).map((hash, index) => { //读取切片数据 const buffer = fs.readFileSync(`${TEMPORARY_FILES}\\${filename}\\${index}`) len += buffer.length return buffer }); //合并切片文件 // 返回一个连接了 list 中所有 Buffer 的新 Buffe const buffer = Buffer.concat(bufferList, len); //在public下创建文件 const ws = fs.createWriteStream(`${STATIC_FILES}\\${filename}`) ws.write(buffer); ws.close(); } catch (error) { console.error(error); } ctx.body = { "url": `${ctx.origin}/${filename}` } }) 结语

文件的切片上传差不多就是这样,不过其中还是有很多问题,比如两个文件的名字一样怎么办,还有文件很大切太多了,那会同时发很多的请求等。有懂的可以评论说一下,博主也不是很懂。对于文件的切片上传,说简单理解了后感觉也是挺简单的,说难吧也是挺难的,这代码没有人的指点出不来出不来或许还是菜了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3